package fr.n7.sma.util.property;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class PropertyManager<Key> {

	Map<Key, Map<Class<? extends Object>, List<PropertyListener<Key>>>> interestedListeners;
	List<PropertyListener<Key>> inAllInterestedListeners;
	
	Map<Key, Map<Class<? extends Object>, Object>> values;

	public PropertyManager(){
		interestedListeners = new ConcurrentHashMap<Key, Map<Class<? extends Object>,List<PropertyListener<Key>>>>();
		inAllInterestedListeners = new ArrayList<PropertyListener<Key>>();
		values = new ConcurrentHashMap<Key, Map<Class<? extends Object>,Object>>();
	}
	
	public <T extends Object> T set(Key key, Class<T> c, T value){
		if(c.isInstance(value)){
			Map<Class<? extends Object>, Object> ls = values.get(key);
			if(ls == null){
				ls = new HashMap<Class<? extends Object>, Object>();
				values.put(key, ls);
			}
			Object result = ls.put(c, value);
			if(result == null){
				if(value != null){
					notifyListeners(key, c, value);
				}
			}else if(!result.equals(value)){
				notifyListeners(key, c, value);
			}
			return c.cast(result);
		}else{
			throw new InvalidParameterException("The value must be an object of class gived as parameter.");
		}
	}

	public <T extends Object> void propertyChanged(Key key, Class<T> c){
		notifyListeners(key, c, c.cast(values.get(key).get(c)));
	}
	
	public <T extends Object>T get(Key key, Class<T> propertyClass){
		Map<Class<? extends Object>, Object> map = values.get(key);
		if(map == null){
			return null;
		}else{
			return propertyClass.cast(map.get(propertyClass));
		}
	}

	public void addListener(PropertyListener<Key>... ls){
		for(PropertyListener<Key> l : ls){
			inAllInterestedListeners.add(l);
		}
	}

	public void addListener(Key key, Class<? extends Object> propertyClass, PropertyListener<Key>... ls){
		Map<Class<? extends Object>, List<PropertyListener<Key>>> ls0 = interestedListeners.get(key);
		if(ls0 == null){
			ls0 = new HashMap<Class<? extends Object>, List<PropertyListener<Key>>>();
			interestedListeners.put(key, ls0);
		}
		List<PropertyListener<Key>> ls1 = ls0.get(propertyClass);
		if(ls1 == null){
			ls1 = new ArrayList<PropertyListener<Key>>();
			ls0.put(propertyClass, ls1);
		}
		for(PropertyListener<Key> pl : ls){
			ls1.add(pl);
		}
	}
	
	@SuppressWarnings("unchecked")
	public <T extends Object>void notifyListeners(Key key, Class<T> c) {
		Map<Class<? extends Object>, Object> ls = values.get(key);
		if(ls == null){
			ls = new HashMap<Class<? extends Object>, Object>();
			values.put(key, ls);
		}
		T value = (T)ls.get(c);
		Map<Class<? extends Object>, List<PropertyListener<Key>>> map = interestedListeners.get(key);
		if(map != null){
			for(PropertyListener<Key> pl : map.get(c)){
				pl.propertyChanged(new PropertyChangedEvent<Key>(this, c, value, key));
			}
		}
		for(PropertyListener<Key> pl : inAllInterestedListeners){
			pl.propertyChanged(new PropertyChangedEvent<Key>(this, c, value, key));
		}
	}
	
	private <T extends Object>void notifyListeners(Key key, Class<T> c, T value) {
		Map<Class<? extends Object>, List<PropertyListener<Key>>> map = interestedListeners.get(key);
		if(map != null){
			for(PropertyListener<Key> pl : map.get(c)){
				pl.propertyChanged(new PropertyChangedEvent<Key>(this, c, value, key));
			}
		}
		for(PropertyListener<Key> pl : inAllInterestedListeners){
			pl.propertyChanged(new PropertyChangedEvent<Key>(this, c, value, key));
		}
	}
}
